Skip to content

release: dev → prod — 2026-07-17 (booking calendar correctness: scheduling-timezone limits + multi-tab allocation guard) - #999

Merged
teetangh merged 81 commits into
prodfrom
release/dev-to-prod-2026-07-17
Jul 17, 2026
Merged

release: dev → prod — 2026-07-17 (booking calendar correctness: scheduling-timezone limits + multi-tab allocation guard)#999
teetangh merged 81 commits into
prodfrom
release/dev-to-prod-2026-07-17

Conversation

@teetangh

Copy link
Copy Markdown
Contributor

This release ships PR #998 (booking calendar correctness): ADR B9 introduces scheduling-timezone limit bucketing consistent across client and server, ADR B10 adds an initialAllocation multi-tab 409 guard backed by advisory-lock atomicity, and the client now wires an Idempotency-Key with 409 recovery UX and requested-path parity, alongside a toast queue + message catalog and a 631-test matrix. It also carries everything merged to dev since the last release (PR #947, 2026-07-01): PR #967 (SonarCloud config), PR #976 and #995 (CTO subsystem audit pack + fix wave, including moderation side-effects work from #974/#693), and related Sonar cleanup and stream-authz fixes.

No DB schema changes or migrations are included in this release.

🤖 Generated with Claude Code

teetangh and others added 30 commits July 10, 2026 11:23
Adds sonar-project.properties + a non-blocking SonarQube Cloud scan step in
the test-and-build CI job, reusing the lcov coverage already emitted by jest
(no extra test run). Coverage reporter pinned to lcov so the report path stays
stable regardless of jest defaults.

Requires a one-time setup: create the project in SonarQube Cloud, fill the org
+ project keys in sonar-project.properties, and add the SONAR_TOKEN repo secret.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Keep sonar-project.properties (org + projectKey + exclusions, read by
SonarQube Cloud Automatic Analysis) and drop the CI-based scan step + jest
lcov reporter — Automatic Analysis stays enabled for now, and CI analysis
would conflict with it. Re-add the CI step when we switch on coverage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s (Part of #693)

BetterAuth admin plugin (banned/banReason/banExpires, defaultRole
CONSULTEE) + two-phase orchestrator in lib/moderation: transactional
ban flags, session revocation, earnings hold, profile unverification,
review soft-delete; best-effort bulk cancel with 100% refunds, Stream
revoke/deactivate, Novu notifies — outcome persisted to
ModerationAction.sideEffects. Fixes the dashboard actionType contract
mismatch (every staff action 400'd) and adds suspension duration
presets. Starts #725 Tier-1 (admin plugin adoption).

Schema (pending coordinated db push): User ban fields,
Session.impersonatedBy, ConsultantReview.deletedAt (+ public-read
filters), ModerationAction.sideEffects, CancellationReason.MODERATION.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…Part of #899)

Stream's server-side API bypasses its permission system, so the gate
must live app-side: token minting now requires a session, is bound to
the caller's own userId (staff/admin excepted), and refuses banned
users — a revoked token was otherwise trivially re-mintable.
addMemberToChannel (previously unguarded, zero callers) now allows
only staff/admin or the channel creator, without lazy channel creation
for non-privileged callers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ects (Part of #734)

The payment-success and payment-failure notification paths dragged
4–5-level includes (full User + profile rows across all four
appointment shapes) to read an id and a name. OPT-1 from the retired
payment task file.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-chat decisions

All 13 task files verified superseded — fixed in code, tracked in open
issues (#738/#899/#701/#863), or drafts of issue bodies that exist.
The 2026-07-10 issue triage plan moves into docs/roadmap so it is
tracked. Stream docs now state the deliberate consultee↔consultee DM
block (group/event channels are the sanctioned shared space), the
deterministic channel-id design, and moderation token
revocation/deactivation. New ADR covers the #693 enforcement design.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…fe Set iteration, number money fields

Part of #693.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…merged 404 branch, session-mocked stream tests

Gemini triage on PR #974: suspension-days input NaN sentinel so the
field can be cleared (Suspend disables until valid); single 404
conditional in the review GET. The channel-actions suite mocks
auth-server/auth-helpers (jest can't parse better-auth ESM) and gains
3 tests for the #899 addMemberToChannel guard.

Part of #693, #899.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e server errors in toast, roadmap addendum

The non-throwing Novu wrappers return {success:false} on delivery
failure, so sideEffects.notification now reads the flag instead of
always "ok". The staff toast surfaces the API's 400/409 message.
Roadmap snapshot gains a dated #693-implemented addendum; the ADR
names the best-effort reconciliation path as an explicit follow-up
(the 409 guard defers it, thread left open).

Part of #693.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Capture high-level gaps, unhappy paths, and A/B/C design questions across
booking, finances, enterprise, stream, compliance, and secondary systems.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add a Recommendation under every open question across the audit pack,
naming the preferred approach and briefly rejecting the alternatives for
Familiarise's current launch posture.

Co-authored-by: Cursor <cursoragent@cursor.com>
Document Stream 14-day S3 retention vs cron→Supabase transfer limits,
pricing, concurrency failure modes, and recommend Stream external S3
plus durable workflows over GH Actions cron at medium-to-massive scale.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add focused enterprise issues covering reachable-path done matrices,
host-agency product coverage, org checkout/refunds/payouts trust holes,
KYB/compliance gates, concurrency/chaos gaps, and multi-device abuse.

Co-authored-by: Cursor <cursoragent@cursor.com>
Any caller could mint any user's Stream identity and every user was a global Stream admin.

Part of #899
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ent at checkout

#971 shipped ProgramConsultantAllowlist and Membership.exclusiveEngagement
as write-only stubs: the flag's only reference was a default-false write and
the allowlist was never read at checkout. Enforcement now runs inside
revalidateInsideLock, under the distributed lock, exactly where the ADR-18
comment pointed: allowlist rows on the funding Program restrict org-sponsored
bookings to listed consultants (zero rows = open network), and an ACTIVE
membership with exclusiveEngagement blocks the consultant's independent
(non-org-owned) plans. Defaults are unchanged, so behaviour only shifts when
an operator opts in. The "hide" half of exclusivity (marketplace visibility
filtering) remains future work per ADR 18.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…per pair, recompute rating on all mutations

POST /api/user/reviews trusted the body's consulteeProfileId, letting any
user post reviews as any consultee, with no check that they ever booked the
consultant, no per-pair uniqueness, and no recompute of the denormalized
ConsultantProfile.rating that explore sort/filter reads. Reviews are now
authored only as the session's own consultee profile, gated on a completed
booking (COMPLETED consultation/subscription, a held slot, or a
COMPLETED/CONVERTED trial), deduped by @@unique on
(consultantProfileId, consulteeProfileId) with P2002 mapped to 409, and
every create/update/delete recomputes the rating in-transaction via a
shared lib/reviews helper. Also drops the email OR-clause from the public
consultant search, which was a PII enumeration key.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-07-11-2

chore(release): back-merge prod → dev after the 2026-07-11-2 release
…hannels

Extends least-privilege role mapping: video-call host and event-channel
creators get channel-scoped grants instead of relying on global admin.

Part of #899
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…loads, delete objects on retention

Phase-0 hardening: wire the dead queueRecordingTransfer so SUPABASE_PERMANENT
recordings enqueue on recording_ready (cron becomes backstop); stream uploads
instead of buffering the whole blob; retention now deletes the Supabase object,
not just the DB row; bounded transfer concurrency; <72h backlog alert; doc drift.

Part of #899
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ENTS gateway enum

Gateway-evaluation ADR marks Lemon/XFlow REMOVE; deletes their checkout stubs,
webhook routes, config, seed refs and doc mentions (and stray day-pass docs).
Stripe retained. Adds DODO_PAYMENTS enum value for post-MVP evaluation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…endly conflict

Reuses the checkout tentative-hold machinery so a NOTIFIED waitlist user gets a
real seat reservation for the response window instead of an FCFS race at
checkout; join is transactional with a friendly 409 on double-join.

Part of #837
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…dempotency, widen reconcile, tx CRUD, shard auto-allocate lock

A single/multi-session subscription reschedule no longer flips the whole
subscription to PENDING (#448); allocate endpoints dedupe double-submits via
Appointment.allocationIdempotencyKey (#837); the reconcile detector uses the
canonical occupancy filter so unpaid/tentative overlaps are caught; webinar/
class plan deletes wrap guard-check+delete in a Serializable tx to close the
check-then-act race; the auto-allocate Redis lock shards by target day so
non-overlapping-day allocations for one consultant no longer serialize (#860),
backstopped by the #440 GiST exclusion constraint.

Closes #860
Part of #837, #448
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ollaborator perms, legal constants, DPDP docstring

Trust & safety sweep bundling seven correctness/compliance fixes:
- Referral stash no longer wiped when landing without ?ref= (#891).
- Novu triggers carry a deterministic transactionId (dedup) and fail loud
  via Sentry when unconfigured in prod; reminders pass a per-window key.
- Payment-linked support tickets dedup against an open ticket; staff status
  transition is now a status-guarded CAS (updateMany).
- Collaborator permission booleans are set at invite time; canSeeAttendees
  is enforced on the participant-roster endpoints.
- Legal constants set name=Practitionist and drop the [ADDRESS] placeholder
  and its rendered blocks; contact emails kept as loud TODO placeholders.
- DPDP header docstring corrected to describe the live fail-closed behavior.
- Webinar/class XOR added to the check-constraints.sql sidecar.

Closes #891
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eeze wallet on ledger drift

Two verified webhook paths left captured funds stuck on manual ops, and a
detected wallet cache/journal drift was only reported. Reuse the existing
refundPayment infra to auto-refund both captured-but-blocked cases (manual
recovery preserved as the fallback if the refund call itself throws), and
introduce a scoped wallet-spend freeze the checkout path honors on drift.

Part of #837
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s, correct session-revoke claim on membership removal

Kills last-write-wins races: compare-and-swap on onboarding final submit
(guarded on onboardingCompleted) and SSO settings PATCH (optimistic
expectedVersion lock); SCIM user provisioning now honors the unverified-org
seat governance (UNVERIFIED_ORG_SEAT_CAP) the invite path already enforces.

For membership removal, no true server-side revoke-by-userId exists: the
BetterAuth admin plugin (revokeUserSessions) is not installed and core
revokeSession needs the target's own session token, so the misleading
"calls revokeSession" comment is corrected to describe the real mechanism —
the sessionGeneration bump the customSession reader honors, with the 24h
cookie-cache window as backstop.

Part of #724, #840
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t earnings, wire domain-verify gate

The INVOICE-sponsor trust park was mis-wired: it keyed on the expert's HOST
org instead of the sponsoring org that owes the invoice, consultant earnings
were never parked at all, and the domain-verification gate had zero callers.
An unverified sponsor could book, accrue real consultant/org payables, and
ghost the invoice (#687 threat model).

- E-01: key the PENDING_TRUST decision on payment.organizationId (the sponsor),
  decided once and applied to consultant, primary-org and collaborator-org rows.
- E-02: park ConsultantEarnings under the same sponsor gate; extend the
  release-valve cron to promote parked consultant rows (joined via Payment).
- K-02: call assertVerifiedDomainOrThrow on the INVOICE checkout accrual path
  and the fundingSource->INVOICE transition.
- Host-dashboard honesty: gate host-earnings query/route on ENABLE_HOST_ORGS.

Part of #687, #837
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…fication

The platform promised a full refund on a consultant no-show but had no code
path — only the MeetingAttendance foundation data existed. Add an hourly job
that detects confirmed consultant no-shows (consultee joined, consultant never
did, past a conservative grace window), full-refunds via B1's refundPayment,
cancels the booking, and notifies both parties. Idempotent via a status CAS
claim plus refundPayment's refundable-balance guard. No schema changes.

Closes #471
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Earnings flipped to PAID the moment a payout BATCH was created — before
any gateway wire / UTR, and even with ENABLE_LIVE_PAYOUTS off. Finance
exports and consultant/host dashboards therefore claimed money had moved
when no cash had left. Introduce a BATCHED status: batch creation stages
earnings as BATCHED; PAID is written only when the payout row reaches
COMPLETED (+ UTR). Batch-eligibility excludes BATCHED, and every failure
/ reject / reversal path that predates COMPLETED releases BATCHED back to
READY.

Part of #837
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… window

Option B (compensating-write, not tx-fold). Folding earnings + the BOOKING
journal into the checkout/webhook tx is unsafe: createEarningsFromPayment opens
its OWN withSerializableRetry(prisma.$transaction(Serializable)) — the #896
waiver-race guard needs to abort + re-run the whole tx, impossible nested — and
the double-entry journal lives inside that same internal tx, so folding would
balloon the outer tx's lock footprint/duration. So we keep it post-commit but
stop pretending success: on failure we now page (recordSystemError → Sentry
ERROR + durable SystemEvent) instead of a silent level:warning. The guaranteed
idempotent healer is the existing data-state sync-payment-earnings scan
(SUCCEEDED payment + earnings:none), which keys on row state, not on this
marker. B1 auto-refund / B2 PENDING_TRUST park / B3 BATCHED status are untouched
— initialEarningStatus logic stays inside createEarningsFromPayment.

Part of #837
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
teetangh and others added 23 commits July 16, 2026 19:43
Part of #837
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… onto dev

Part of #693
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…gin initializes at build

Part of #693
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The real build fix is the BetterAuth admin roles config (888a7ae); the
analytics route builds fine without force-dynamic, same as on dev.

Part of #693
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Merges feature/moderation-actions-693 (#974) into the integration branch so
#995 merges cleanly after #974. Union resolutions (not take-one-side):

- stream.action.ts assertCanMintToken: keep #995's getSession(true) cache-bypass
  AND #974's banned-user check AND the isPrivileged/forUserId gate.
- lib/reviews.ts recomputeConsultantRating: add deletedAt:null so soft-removed
  reviews (#974) don't count — the DRY helper (#987) now honors soft-delete;
  staff-moderation route uses the helper instead of an inline duplicate.
- user/reviews/[id] select: union consulteeProfileId + consultantProfileId
  (#987 recompute) + deletedAt (#974 edit-guard).
- channel.action.ts auto-merged: retains both #995's channel_moderator grant
  and #974's addMemberToChannel authz gate.
- tasks/1.txt: honor #974 task-folder retirement (deleted).

Part of #995, #693
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…r.NaN

Review triage: doc now states re-run is blocked by the 409 guard (remediation
is manual via the best-effort-persisted sideEffects summary); NaN -> Number.NaN.

Part of #693
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The earlier prefer-array-to-sorted fix left toSorted() without a compare
function, which Sonar flags as an unreliable string sort (new_reliability_rating
D). Add localeCompare — matches the payload sort above and makes the dedup key
deterministic.

Part of #995
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Part of #693
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Part of #995
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ating

Number.parseInt("7.5") silently became 7; validate as an integer and fall back
to the NaN sentinel (Suspend stays disabled) for non-integers. (CodeRabbit)

Part of #693
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…p/no-show fns (S3776)

Helper extraction only — no behavior change. Money/moderation orchestrators
were accepted in Sonar instead.

Part of #995
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…into integration/cto-audit-wave

# Conflicts:
#	app/dashboard/staff/[staffId]/(features)/moderation/page.tsx
…ion/cancel orchestrators (S3776)

Helper extraction only — byte-identical behavior (phase split, CAS guards,
refund/hold/ban ordering, and per-step catch-and-record all preserved).

Part of #693
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat(moderation): real side-effects for staff actions (#693) + Stream authz guards + task-folder retirement
…anup

CTO Sub-Audit Fix Wave + moderation (#693) — money/security/booking integrity, Sonar-clean
docs(bugs): CTO subsystem architecture audit pack
Automatic Analysis ignores sonar-project.properties (CI-scanner-only); add
.sonarcloud.properties so the exclusions (docs/node_modules/seeds/generated)
take effect on the current Automatic Analysis, and correct the misleading
comment. sonar-project.properties stays staged for the future CI coverage scan.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… CI-scan staging)

chore(ci): add sonar-project.properties for SonarQube Cloud Automatic Analysis
… allocation, requested-path parity (#998)

* fix(booking): scheduling-timezone limit buckets, guarded + idempotent allocation, requested-path parity

Unify every daily/weekly limit bucket on the event's schedulingTimezone
(default Asia/Kolkata) via shared SlotCalculationService keys across the
client guards, the client auto-allocator, and the server validators
(ADR B9); add the initialAllocation multi-tab 409 guard (ADR B10); send
Idempotency-Key from all client allocate paths with 409 recovery UX;
fix the requested-slots path ignoring plan totals and in-progress
reschedule reductions; remove the callsPerWeek*4*months fallback; queue
toasts through a single message catalog; extract the selection
validators to a pure, unit-tested module.

Part of the Request Calendar work tracked in #997.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(booking): address PR review — CI TZ pins, Sonar gate, guard atomicity, requested-mode idempotency

CI: replace machine-local getDay() assertions with scheduling-timezone /
UTC-explicit checks (suite now green under UTC, IST, and New York).
Sonar: split validateEventSlots into per-event validators (complexity
49→dispatcher), explicit sort comparator, crypto-only key fallback,
optional chains, readonly formatter cache, drop redundant jumps and an
always-truthy guard. CodeRabbit: advisory xact lock makes the
initialAllocation guard atomic across modes for lock-less group events;
requested mode now replays and stamps Idempotency-Keys; the requests
tab attempt key moves to a ref (double-click safe); allocation response
typed as appointment DTOs; getSlotLimits honors the plan total;
manual allocate rejects incomplete per-day runs; per-day cap sorts its
input; scheduling timezone threaded into auto-expand and progress
helpers. Adds race-window and non-default-timezone tests.

Part of #998 review triage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(booking): CI type errors — DTO-typed mock and crypto feature-detection narrowing

lib.dom types every Crypto member as present, so an `in` check collapses
the fallback branch to never; detect via optional access instead. Mock
allocation data as an array to match the new DTO type.

Part of #998 review triage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(booking): round-2 review — locked replay in the in-txn guard, run-aware session completeness

The advisory-locked guard now re-checks idempotent replay after acquiring
the event lock (all three modes), so a same-key double submit that lost
the pre-transaction race replays the winner's batch instead of 409ing.
Day completeness checks decompose each day into complete CONSECUTIVE
sessions via countSessionsForDay — a bare length-modulo let disconnected
fragments masquerade as complete sessions.

Part of #998 review triage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@netlify

netlify Bot commented Jul 17, 2026

Copy link
Copy Markdown

Deploy Preview for familiarise ready!

Name Link
🔨 Latest commit e4cb92d
🔍 Latest deploy log https://app.netlify.com/projects/familiarise/deploys/6a5a258f838cac0008d1645d
😎 Deploy Preview https://deploy-preview-999--familiarise.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 70 (🟢 up 9 from production)
Accessibility: 99 (🟢 up 3 from production)
Best Practices: 92 (🟢 up 9 from production)
SEO: 100 (no change from production)
PWA: -
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e1e02df6-65b5-4558-bd70-fe4f902fab7a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch release/dev-to-prod-2026-07-17

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sonarqubecloud

Copy link
Copy Markdown

@teetangh teetangh self-assigned this Jul 17, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a comprehensive correctness sweep across the booking calendar, payment, and moderation subsystems. Key changes include unifying daily and weekly limit bucketing to the event's scheduling timezone, introducing a multi-tab allocation guard with client-side idempotency keys, automating consultant no-show refunds, and fully implementing moderation action side-effects (such as user bans, session revocations, and bulk cancellations). Additionally, the payment gateway options are cleaned up by removing Lemon Squeezy and XFlow, and a wallet-spend freeze is introduced for accounts with ledger-cache drift. Feedback from the review warns of critical database migration risks: the new unique constraint on reviews, the removal of payment gateway enum values, and the new collaborator CHECK constraint will all cause migration failures if existing production data is not cleaned up or de-duplicated prior to deployment.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread prisma/schema.prisma
updatedAt DateTime @updatedAt

// One review per consultee per consultant — POST maps P2002 here to 409.
@@unique([consultantProfileId, consulteeProfileId])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Adding a unique constraint on [consultantProfileId, consulteeProfileId] is a great improvement for data integrity. However, this will fail during migration if any duplicate reviews for the same consultant-consultee pair already exist in the database. The bugs/README.md file acknowledges this and suggests de-duplication. Please ensure a data migration script or manual cleanup is performed before this change is deployed to production to avoid deployment failures.

Comment thread prisma/schema.prisma
Comment on lines 3802 to 3807
enum PaymentGateway {
STRIPE
RAZORPAY
LEMON_SQUEEZY
XFLOW
DODO_PAYMENTS // post-MVP: evaluation pending
CARD
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The PR description states "No DB schema changes or migrations are included in this release", but this is a potentially breaking schema change. Removing LEMON_SQUEEZY and XFLOW from the PaymentGateway enum will cause migration failures if there is existing data in production using these values. The bugs/README.md file even mentions that this change should be deferred. Please clarify the strategy for this schema change in a production release.

Comment on lines +173 to +174
ALTER TABLE "Collaborator" ADD CONSTRAINT "collaborator_plan_xor"
CHECK (("webinarPlanId" IS NULL) <> ("classPlanId" IS NULL));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Adding this CHECK constraint is good for data integrity. However, it will cause the migration to fail if there are any existing Collaborator records where both webinarPlanId and classPlanId are non-null, or where both are null. Please ensure the existing data in production conforms to this constraint before deploying.

@teetangh
teetangh merged commit 97233eb into prod Jul 17, 2026
21 checks passed
@teetangh
teetangh deleted the release/dev-to-prod-2026-07-17 branch July 17, 2026 13:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant